#region Using References

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using Beaker.OpenCube.CubeManager.Model.Interfaces;

#endregion

namespace Beaker.OpenCube.CubeManager.Model.Framework
{
	public sealed class EntitySet<TEntity, TOwner>
		: ISet<TEntity>
		where TEntity : Entity
		where TOwner : Entity
	{
		private readonly HashSet<TEntity> _underlyingSet = new HashSet<TEntity>();
		private readonly TOwner _owner;
		private readonly Action<TEntity, TOwner> _setParent;
		private readonly Func<TEntity, ISet<TOwner>> _getSetOnOther; 

		/// <summary>
		/// Initializes the EntitySet for an 1-n relation.
		/// </summary>
		/// <param name="owner">The owner of the set.</param>
		/// <param name="setParent">The action that will set the parent on the given child entity.</param>
		public EntitySet( TOwner owner, Action<TEntity, TOwner> setParent )
		{
			Contract.Requires<ArgumentNullException>( owner != null );
			Contract.Requires<ArgumentNullException>( setParent != null );

			_owner = owner;
			_setParent = setParent;
		}

		/// <summary>
		/// Initializes the EntitySet for an n-n relation.
		/// </summary>
		/// <param name="owner">The owner of the set.</param>
		/// <param name="getSetOnOther">The function that will get the set to the other entity.</param>
		public EntitySet( TOwner owner, Func<TEntity, ISet<TOwner>> getSetOnOther )
		{
			Contract.Requires<ArgumentNullException>( owner != null );
			Contract.Requires<ArgumentNullException>( getSetOnOther != null );

			_owner = owner;
			_getSetOnOther = getSetOnOther;
		}

		/// <summary>
		/// Initializes the EntitySet for an n-n relation where the other 'side' has no back-reference set.
		/// </summary>
		/// <param name="owner">The owner of the set.</param>
		public EntitySet( TOwner owner )
		{
			Contract.Requires<ArgumentNullException>( owner != null );

			_owner = owner;
		}

		/// <summary>
		/// Gets the owner of this entity set.
		/// </summary>
		/// <value>
		/// The owner of the entity set.
		/// </value>
		public Entity Owner
		{
			get
			{
				Contract.Ensures( Contract.Result<IIdentifiable>() != null );
				return _owner;
			}
		}

		/// <summary>
		/// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.
		/// </summary>
		/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
		void ICollection<TEntity>.Add( TEntity item )
		{
			Add( item );
		}

		/// <summary>
		/// Modifies the current set so that it contains all elements that are present in either the current set or the specified collection.
		/// </summary>
		/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
		void ISet<TEntity>.UnionWith( IEnumerable<TEntity> other )
		{
			_underlyingSet.UnionWith( other );
		}

		/// <summary>
		/// Modifies the current set so that it contains only elements that are also in a specified collection.
		/// </summary>
		/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
		void ISet<TEntity>.IntersectWith( IEnumerable<TEntity> other )
		{
			_underlyingSet.IntersectWith( other );
		}

		/// <summary>
		/// Removes all elements in the specified collection from the current set.
		/// </summary>
		/// <param name="other">The collection of items to remove from the set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
		void ISet<TEntity>.ExceptWith( IEnumerable<TEntity> other )
		{
			_underlyingSet.ExceptWith( other );
		}

		/// <summary>
		/// Modifies the current set so that it contains only elements that are present either in the current set or in the specified collection, but not both. 
		/// </summary>
		/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
		void ISet<TEntity>.SymmetricExceptWith( IEnumerable<TEntity> other )
		{
			_underlyingSet.SymmetricExceptWith( other );
		}

		/// <summary>
		/// Determines whether a set is a subset of a specified collection.
		/// </summary>
		/// <returns>
		/// true if the current set is a subset of <paramref name="other"/>; otherwise, false.
		/// </returns>
		/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
		bool ISet<TEntity>.IsSubsetOf( IEnumerable<TEntity> other )
		{
			return _underlyingSet.IsSubsetOf( other );
		}

		/// <summary>
		/// Determines whether the current set is a superset of a specified collection.
		/// </summary>
		/// <returns>
		/// true if the current set is a superset of <paramref name="other"/>; otherwise, false.
		/// </returns>
		/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
		bool ISet<TEntity>.IsSupersetOf( IEnumerable<TEntity> other )
		{
			return _underlyingSet.IsSupersetOf( other );
		}

		/// <summary>
		/// Determines whether the current set is a proper (strict) superset of a specified collection.
		/// </summary>
		/// <returns>
		/// true if the current set is a proper superset of <paramref name="other"/>; otherwise, false.
		/// </returns>
		/// <param name="other">The collection to compare to the current set. </param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
		bool ISet<TEntity>.IsProperSupersetOf( IEnumerable<TEntity> other )
		{
			return _underlyingSet.IsProperSupersetOf( other );
		}

		/// <summary>
		/// Determines whether the current set is a proper (strict) subset of a specified collection.
		/// </summary>
		/// <returns>
		/// true if the current set is a proper subset of <paramref name="other"/>; otherwise, false.
		/// </returns>
		/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
		bool ISet<TEntity>.IsProperSubsetOf( IEnumerable<TEntity> other )
		{
			return _underlyingSet.IsProperSubsetOf( other );
		}

		/// <summary>
		/// Determines whether the current set overlaps with the specified collection.
		/// </summary>
		/// <returns>
		/// true if the current set and <paramref name="other"/> share at least one common element; otherwise, false.
		/// </returns>
		/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
		bool ISet<TEntity>.Overlaps( IEnumerable<TEntity> other )
		{
			return _underlyingSet.Overlaps( other );
		}

		/// <summary>
		/// Determines whether the current set and the specified collection contain the same elements.
		/// </summary>
		/// <returns>
		/// true if the current set is equal to <paramref name="other"/>; otherwise, false.
		/// </returns>
		/// <param name="other">The collection to compare to the current set.</param><exception cref="T:System.ArgumentNullException"><paramref name="other"/> is null.</exception>
		bool ISet<TEntity>.SetEquals( IEnumerable<TEntity> other )
		{
			return _underlyingSet.SetEquals( other );
		}

		/// <summary>
		/// Adds an entity to the current set and returns a value to indicate if the entity was successfully added.
		/// </summary>
		/// <param name="item">The entity to add to the set.</param>
		/// <returns>
		/// true if the entity is added to the set; false if the entity is already in the set.
		/// </returns>
		public bool Add( TEntity item )
		{
			if( _underlyingSet.Add( item ) )
			{
				if( _getSetOnOther != null )
				{
					// now add to reverse set
					_getSetOnOther( item ).Add( _owner );
					return true;
				}
				if( _setParent != null )
				{
					// now set the reverse reference
					_setParent( item, _owner );
					return true;
				}
			}

			return false;
		}

		/// <summary>
		/// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
		/// </summary>
		/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. </exception>
		public void Clear()
		{
			_underlyingSet.Clear();
		}

		/// <summary>
		/// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value.
		/// </summary>
		/// <returns>
		/// true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false.
		/// </returns>
		/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
		public bool Contains( TEntity item )
		{
			return _underlyingSet.Contains( item );
		}

		/// <summary>
		/// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
		/// </summary>
		/// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param><param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param><exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null.</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="arrayIndex"/> is less than 0.</exception><exception cref="T:System.ArgumentException">The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.</exception>
		void ICollection<TEntity>.CopyTo( TEntity[] array, int arrayIndex )
		{
			_underlyingSet.CopyTo( array, arrayIndex );
		}

		/// <summary>
		/// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.
		/// </summary>
		/// <returns>
		/// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
		/// </returns>
		/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param><exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
		public bool Remove( TEntity item )
		{
			if( _underlyingSet.Remove( item ) )
			{
				if( _setParent != null )
				{
					// removed, so update the reverse 
					_setParent( item, null );
					return true;
				}

				if( _getSetOnOther != null )
				{
					_getSetOnOther( item ).Remove( _owner );
					return true;
				}
			}

			return false;
		}

		/// <summary>
		/// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
		/// </summary>
		/// <returns>
		/// The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.
		/// </returns>
		public int Count
		{
			get { return _underlyingSet.Count; }
		}

		/// <summary>
		/// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.
		/// </summary>
		/// <returns>
		/// true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.
		/// </returns>
		bool ICollection<TEntity>.IsReadOnly
		{
			get { return false; }
		}

		/// <summary>
		/// Returns an enumerator that iterates through the collection.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
		/// </returns>
		/// <filterpriority>1</filterpriority>
		IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
		{
			return _underlyingSet.GetEnumerator();
		}

		/// <summary>
		/// Returns an enumerator that iterates through a collection.
		/// </summary>
		/// <returns>
		/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
		/// </returns>
		/// <filterpriority>2</filterpriority>
		IEnumerator IEnumerable.GetEnumerator()
		{
			return _underlyingSet.GetEnumerator();
		}
	}
}